#pragma once

enum ShadowTech
{
	ST_NO = 0,		// no shadow
	ST_SSM,			// standard shadow map
	ST_PSM,			// perspective shadow map	

	ST_NUM,
};

extern wchar_t* ShadowTechName[];


enum ShadowRes
{
	SR_2048 = 0,
	SR_1024,
	SR_512,
	SR_256,

	SR_NUM,
};

extern wchar_t* ShadowResName[];
extern UINT ShadowMapRes[];

enum ShadowSample
{
	SSD_1x1 = 0,		// Only one sample for each pixel
	SSD_2x2,
	SSD_3x3,
	SSD_4x4,
	SSD_5x5,
	SSD_6x6,
	SSD_7x7,
	SSD_8x8,

	SSD_NUM,
};

extern wchar_t* ShadowSampleName[];
extern UINT ShadowSampleNum[];


// This solution only applies to directional light source
class ShadowSolution
{
public :
	ShadowSolution(IDirect3DDevice9* deviceIn);
	~ShadowSolution();

	ShadowTech		GetTech() const { return Tech; }
	ShadowRes		GetResolution() const { return Resolution; }
	ShadowSample	GetSample() const { return Sample; }
	UINT			GetSampleNum() const { return ShadowSampleNum[Sample]; }
	D3DXVECTOR4*	GetSampleOffset() const;
	const wchar_t*	GetTechDesc() const { return ShadowTechName[Tech]; }
	const wchar_t*	GetResDesc() const { return ShadowResName[Resolution]; }
	const wchar_t*	GetSampleDesc() const { return ShadowSampleName[Sample]; }
	
	const D3DXVECTOR3&	GetLightDirection() const { return LightDirection; }
	const D3DXVECTOR3&	GetCameraPosition() const { return CameraPosition; }
	const D3DXVECTOR3&	GetSceneMin() const { return SceneMin; }	
	const D3DXVECTOR3&	GetSceneMax() const { return SceneMax; }	
	
	const D3DXMATRIXA16& GetShadowGenMatrix() const { return ShadowGen; }
	const D3DXMATRIXA16& GetShadowUseMatrix() const { return ShadowUse; }
	bool ShouldInvertDepthFunc() const { return InvertDepthFunc; }
	float GetDepthBias() const { return DepthBias; }
	float GetFarOverNear() const { return FarOverNear; }

	void SetTech(ShadowTech techIn);
	void SetResolution(ShadowRes resIn);
	void SetSample(ShadowSample sampleIn)	{ Sample = sampleIn; }

	void SetLightDirection(const D3DXVECTOR3& lightDir);
	void SetCameraPosition(const D3DXVECTOR3& cameraPos);
	void SetCameraDirection(const D3DXVECTOR3& cameraDirection);
	void SetCameraUp(const D3DXVECTOR3& cameraUp);
	void SetCameraParam(float nearClip, float farClip, float aspect, float fovy);
	void SetSceneMin(const D3DXVECTOR3& sceneMin);
	void SetSceneMax(const D3DXVECTOR3& sceneMax);
	void SetDepthBias(float bias) { DepthBias = bias; }
	void SetFarOverNear(float farovernear) { FarOverNear = farovernear; }

	void CreateDebugFrustums();
	int  GetDebugFrustumNum() const { return int(DebugFrustum.size()); }
	const Frustum* GetDebugFrustum(int FrustumID) const;
	int	 GetDebugVertexNum() const { return int(DebugVertex.size()); }
	const VEC3* GetDebugVertex(int VertexID) const;
	int  GetDebugVertex2DNum() const { return int(DebugVertex2D.size()); }
	const VEC2* GetDebugVertex2D(int VertexID) const;

	IDirect3DTexture9* GetDepthTex() { return DepthTexture; }
	HRESULT DumpDepthTexture(TCHAR* filename);

	HRESULT PreRenderDepth();
	HRESULT PostRenderDepth();

	HRESULT OnResetDevice(IDirect3DDevice9* pD3DDevice);
	HRESULT OnLostDevice();

	void UpdateTransform();

protected :
	HRESULT	UpdateBuffer();
	

	void UpdateTransform_SSM();
	void UpdateTransform_PSM();

	// Keep the position, direction, up, fov, aspect parameter fixed, shrink the [nearclip, farclip] range.
	Frustum GetTightEyeFrustum();
	Frustum GetEyeFrustum();

	D3DXMATRIXA16	CubeToTexture;		// from light perspective space to texture space
	D3DXMATRIXA16	ShadowGen;
	D3DXMATRIXA16	ShadowUse;

	bool			TransformDirty; 
	bool			BufferDirty;

	ShadowTech		Tech;
	ShadowRes		Resolution;
	ShadowSample	Sample;

	D3DXVECTOR3		LightDirection;

	D3DXVECTOR3		CameraPosition;
	D3DXVECTOR3		CameraDirection;
	D3DXVECTOR3		CameraUp;
	float			NearClip, FarClip;
	float			Aspect;
	float			FieldOfViewY;

	D3DXVECTOR3		SceneMin;
	D3DXVECTOR3		SceneMax;	// AABB


	bool			InvertDepthFunc;

	float			DepthBias;			// Typically a very small figure
	float			FarOverNear;		// For LSPSM only, (1, INF)
	
	IDirect3DSurface9*	DepthBuffer;
	IDirect3DTexture9*	DepthTexture;

	IDirect3DDevice9*	D3DDevice;

	// Used to store the temp values between Pre- and Post-
	IDirect3DSurface9*	StandardColorTarget;
	IDirect3DSurface9*	StandardDepthTarget;
	D3DVIEWPORT9		StandardViewport;

	// Debug info
	bool				ToCreateDebugInfo;
	vector<Frustum>		DebugFrustum;
	vector<VEC3>		DebugVertex;
	vector<VEC2>		DebugVertex2D;	// on XOY plane, x/y in [0, 1]
};